package com.zmn.brs.business.blacklist;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.zmn.brs.business.interfaces.blacklist.BlacklistBService;
import com.zmn.brs.business.interfaces.record.OperateRecordNewBService;
import com.zmn.brs.common.BrsErrorCodeConsts;
import com.zmn.brs.common.constant.RedisKeyConst;
import com.zmn.brs.common.enums.EventEnum;
import com.zmn.brs.common.enums.PlatEnum;
import com.zmn.brs.common.exception.BrsException;
import com.zmn.brs.common.model.dio.blacklist.BlacklistDIO;
import com.zmn.brs.common.model.mq.OperateRecordMq;
import com.zmn.brs.dubbo.constant.DubboConsts;
import com.zmn.brs.model.bo.event.FrequencyStatisticsBO;
import com.zmn.brs.model.bo.event.RuleExecuteResultBO;
import com.zmn.brs.model.entity.blacklist.BlacklistConfig;
import com.zmn.brs.model.entity.blacklist.BlacklistRedis;
import com.zmn.brs.model.result.OperateRecordResult;
import com.zmn.brs.services.interfaces.blacklist.BlacklistConfigService;
import com.zmn.brs.services.interfaces.blacklist.BlacklistService;
import com.zmn.brs.services.interfaces.whitelist.WhitelistService;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.consts.GateTypeConsts;
import com.zmn.consts.GlobalConsts;
import com.zmn.plat.common.dto.channel.ChannelDRO;
import com.zmn.plat.dubbo.interfaces.channel.ChannelListRemoteService;
import com.zmn.uuc.common.constant.PassConsts;
import com.zmn.uuc.dubbo.interfaces.pass.PassListRemoteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 黑名单BService实现类
 *
 * @author linfeng
 * @since 2020/09/29 15:05
 * @modifier zhangyao 2021/05/06 防刷单接口中安全监控检查规则修改
 */
@Slf4j
@Service
public class BlacklistBServiceImpl implements BlacklistBService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private WhitelistService whitelistService;

    @Resource
    private BlacklistService blacklistService;

    @Resource
    private BlacklistConfigService blacklistConfigService;

    @Resource
    private OperateRecordNewBService operateRecordBService;

    @Reference(version = com.zmn.plat.dubbo.utils.DubboConsts.INTERFACE_VERSION, check = false)
    private ChannelListRemoteService channelListRemoteService;

    @Reference(version = DubboConsts.INTERFACE_VERSION, check = false)
    private PassListRemoteService passListRemoteService;

    /**
     * 描述: 1 天,2 分钟
     *
     * @author wangjie
     * @since 2019/8/5
     **/
    private static final Integer DAY_BLACKLIST_TYPE = 1;
    private static final Integer TIME_BLACKLIST_TYPE = 2;

    @Override
    public Boolean checkBlacklistConstraintRules(BlacklistDIO blacklistDio) {
        if (!this.effectBlacklist()) {
            log.info("系统检查黑白名单检查未启用,msg{}", blacklistDio);
            return true;
        }
        if (Objects.isNull(blacklistDio.getIp()) && Objects.isNull(blacklistDio.getMobile()) && Objects.isNull(blacklistDio.getUserId()) && Objects.isNull(blacklistDio.getChannelId())) {
            log.error("ip,电话,用户id,渠道id,不能同时为null");
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_PANAM_NULL);
        }

        ArrayList<String> dioList = this.getBlacklistDioList(blacklistDio);

        // 是黑名单 直接返回提示
        if (this.checkBlacklist(dioList)) {
            log.error("黑白名单检查，黑名单用户，info:{}，errorCode:{}", JSON.toJSONString(dioList), BrsErrorCodeConsts.ERROR_CODE_BLACK_LIST_USER);
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_BLACK_LIST_USER);
        }
        // 检查渠道
        if (this.checkChannel(blacklistDio.getChannelId())) {
            return true;
        }
        // 是白名单,不参与检查
        if (this.checkWhitelist(dioList)) {
            log.info("黑白名单检查，是白名单,不参与检查，info:{}", JSON.toJSONString(dioList));
            return true;
        }

        /* 安全监控替代次数限制
        // 配置按天检查
        BlacklistConfig configDay = Optional.ofNullable(blacklistConfigService.findByTimeType(DAY_BLACKLIST_TYPE)).orElse(new BlacklistConfig());
        ResponseDTO<List<BlacklistRedis>> checkDayBlacklist = this.checkDayBlacklist(blacklistDio, configDay);

        // 配置按时间段检查
        BlacklistConfig configTime = Optional.ofNullable(blacklistConfigService.findByTimeType(TIME_BLACKLIST_TYPE)).orElse(new BlacklistConfig());
        ResponseDTO<List<BlacklistRedis>> checkTimeBlacklist = this.checkTimeBlacklist(blacklistDio, configTime);

        // 时间段检查 写入redis
        this.batchSubmitBlacklistRedisConfig(checkTimeBlacklist.getData(), configTime.getTimeConfig(), TimeUnit.MINUTES);
        // 天检查 写入redis
        this.batchSubmitBlacklistRedisConfig(checkDayBlacklist.getData(), configDay.getTimeConfig(), TimeUnit.DAYS);*/

        // 安全监控检查
        Boolean checkResult = true;
        try {
            checkResult = operateRecordCheck(blacklistDio);
        } catch (BrsException e) {
            // 如果抛出的异常不是触发安全监控，统一拦截，不返回给调用方，以免影响调用方下单
            Integer errorCode = e.getErrorCode();
            if (Objects.nonNull(errorCode) && errorCode.equals(BrsErrorCodeConsts.ERROR_CODE_TRIGGER_SAFETY_MONITORING)) {
                throw e;
            }
        } catch (Exception e) {
            // 如果抛出其他异常，统一拦截，不返回给调用方，以免影响调用方下单
            log.error("黑白名单检查，安全监控检查失败，抛出异常：{}", e);
        }
        log.info("黑白名单检查，安全监控检查结束，最终结果：{}", checkResult);
        return checkResult;
    }

    /**
     * 安全监控检查
     * @param blacklistDio 黑白名单检查参数
     * @return true：操作成功，false：操作失败
     * @throws
     * @author zhangyao
     * @since 2021/04/26 17:08
     */
    private Boolean operateRecordCheck(BlacklistDIO blacklistDio) {
        if (Objects.isNull(blacklistDio.getOperatorIdentity())) {
            log.error("黑白名单检查，安全监控检查失败，对象身份为空，操作内容：{}", blacklistDio);
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_PANAM_NULL);
        }
        // 对象身份为大客户和渠道，不进行安全监控
        if (com.zmn.consts.GlobalConsts.OPERATE_USER_TYPE_BCUSTOMER == blacklistDio.getOperatorIdentity()
                || com.zmn.consts.GlobalConsts.OPERATE_USER_TYPE_CHANNEL == blacklistDio.getOperatorIdentity()) {
            log.info("黑白名单检查，安全监控检查失败，对象身份为大客户和渠道，不进行监控，操作内容：{}", blacklistDio);
            return true;
        }
        if (Objects.isNull(blacklistDio.getOperatorId())) {
            log.error("黑白名单检查，安全监控检查失败，对象Id为空，操作内容：{}", blacklistDio);
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_PANAM_NULL);
        }
        OperateRecordMq recordMq = OperateRecordMq.builder()
                .gate(blacklistDio.getGate())
                .plat(PlatEnum.getPlatEnum(blacklistDio.getPlat()))
                .channelId(blacklistDio.getChannelId())
                .objectIdentity(blacklistDio.getOperatorIdentity())
                .objectId(blacklistDio.getOperatorId())
                .objectMobile(blacklistDio.getOperatorMobile())
                .objectIp(blacklistDio.getIp())
                .event(EventEnum.getEventEnum(blacklistDio.getEvent()))
                .objectIp(blacklistDio.getIp())
                .createTime(new Date())
                .build();
        if (recordMq.getPlat() != null) {
            // 转换事件编码
            EventEnum event = recordMq.getEvent();
            event = EventEnum.transformEventEnum(event, recordMq.getPlat().getCode());
            recordMq.setEvent(event);
        }
        if (OperateRecordMq.getObjectIdIsSystem(recordMq.getObjectId())) {
            // 对象ID为0或者1，表示对象为系统，此时设置对象身份为用户，使用用户的监控规则（场景：用户点推广链接下的单，此时还没有注册成为用户，没有用户ID）
            recordMq.setObjectIdentity(GlobalConsts.OPERATE_USER_TYPE_USER);
        }
        if (StringUtils.isEmpty(recordMq.getObjectMobile())) {
            // 如果操作人手机号为空，取下单的手机号进行监控
            recordMq.setObjectMobile(blacklistDio.getMobile());
        }
        log.info("黑白名单检查，安全监控检查，操作内容：{}", recordMq);
        OperateRecordResult result = operateRecordBService.eventTriggering(recordMq);
        log.info("黑白名单检查，安全监控检查结果：{}", result);
        // 当前操作未触发安全监控，返回true
        if (result.getResult()) {
            return true;
        }
        if (blacklistDio.getGate() == null || blacklistDio.getEvent() == null) {
            deleteFrequencyStatistics(result.getRuleExecuteResultBOs());
            log.info("黑白名单检查，安全监控检查，下单入口或事件为空，操作参数：{}", blacklistDio);
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_TRIGGER_SAFETY_MONITORING);
        }
        Integer gateType = GateTypeConsts.GATE_TYPE_MAP.get(blacklistDio.getGate());
        if (gateType == null) {
            deleteFrequencyStatistics(result.getRuleExecuteResultBOs());
            log.error("黑白名单检查，安全监控检查，未获取到下单入口类型，下单入口：{}", blacklistDio.getGate());
            throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_TRIGGER_SAFETY_MONITORING);
        }
        // 当前操作不为下单操作，返回true
        if (EventEnum.VALUE_ADDED_WORK_ORDER_PLACE_AN_ORDER.getEventCode() > blacklistDio.getEvent()
                || EventEnum.CNHB_WORK_ORDER_PLACE_AN_ORDER.getEventCode() < blacklistDio.getEvent()) {
            log.error("黑白名单检查，安全监控检查，当前操作不为下单操作，事件编码：{}", blacklistDio.getEvent());
            return true;
        }
        // 下单入口为运营平台类/API类，返回true
        if (gateType.equals(GateTypeConsts.GATE_TYPE_PLAT) || gateType.equals(GateTypeConsts.GATE_TYPE_API)) {
            log.error("黑白名单检查，安全监控检查，下单入口为运营平台类/API类，下单入口：{}", gateType);
            return true;
        }
        deleteFrequencyStatistics(result.getRuleExecuteResultBOs());
        throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_TRIGGER_SAFETY_MONITORING);
    }

    /**
     * 强阻断后工单系统不会下单成功，得移除redis中的已经增加了的的对象频次
     * @param ruleExecuteResultBOs 频次统计业务对象
     * @return void
     * @throws
     * @author zhangyao
     * @since 2021/05/31 17:16
     */
    private void deleteFrequencyStatistics(List<RuleExecuteResultBO> ruleExecuteResultBOs) {
        for (RuleExecuteResultBO ruleExecuteResultBO : ruleExecuteResultBOs) {
            FrequencyStatisticsBO frequencyStatisticsBO = ruleExecuteResultBO.getFrequencyStatisticsBO();
            if (Objects.isNull(frequencyStatisticsBO)) {
                continue;
            }
            operateRecordBService.deleteFrequencyStatistics(frequencyStatisticsBO);
        }
    }

    /**
     * 描述: 先判断是否超过每天限制次数，如果超过每天的限制次数则弹出提示语 每天限制：（次数/每天）手机号限制10次，用户ID限制10次，IP限制1000次 可动态配置
     *
     * @param blacklistDio
     *            黑白名单检查入参对象
     * @param config
     *            配置文件
     * @return com.zmn.common.dto.ResponseDTO<com.zmn.plat.model.entity.blacklist.BlacklistRedis>
     * @author wangjie
     * @since 2019/8/9
     **/
    private ResponseDTO<List<BlacklistRedis>> checkDayBlacklist(BlacklistDIO blacklistDio, BlacklistConfig config) {

        ResponseDTO<BlacklistRedis> responseDto = ResponseDTO.success();
        // 生成redis 配置容器
        List<BlacklistRedis> arrayList = Lists.newArrayList();
        if (Objects.nonNull(blacklistDio.getIp())) {
            responseDto = this.dayBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_IP, blacklistDio.getIp(), config.getIpConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按天检查,ip达到限制条件,tag:{},msg:{},errorCode:{}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_IP, blacklistDio.getIp(),
                        BrsErrorCodeConsts.ERROR_CODE_IP_COUNT_LIMIT_BY_DAY);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_IP_COUNT_LIMIT_BY_DAY);
            }
        }
        if (Objects.nonNull(blacklistDio.getMobile())) {
            responseDto = this.dayBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_MOBILE, blacklistDio.getMobile(), config.getMobileConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按天检查,mobile达到限制条件,tag:{},msg:{},errorCode:{}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_MOBILE, blacklistDio.getMobile(),
                        BrsErrorCodeConsts.ERROR_CODE_MOBILE_COUNT_LIMIT_BY_DAY);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_MOBILE_COUNT_LIMIT_BY_DAY);
            }
        }
        if (Objects.nonNull(blacklistDio.getUserId())) {
            responseDto = this.dayBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_USER_ID, Optional.ofNullable(blacklistDio.getUserId()).orElse(0L).toString(), config.getUserIdConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按天检查,userId达到限制条件,tag:{},msg:{},errorCode={}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_DAY_KEY_USER_ID, blacklistDio.getUserId(),
                        BrsErrorCodeConsts.ERROR_CODE_USERID_COUNT_LIMIT_BY_DAY);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_USERID_COUNT_LIMIT_BY_DAY);
            }
        }
        log.info("黑白名单,按天检查通过,msg{}", JSON.toJSONString(responseDto));
        return ResponseDTO.success(arrayList, responseDto.getMessage());
    }

    /**
     * 描述: 按天配置
     *
     * @param key
     *            redis Key
     * @param hashKey
     *            散列 key
     * @param restrictionCount
     *            限制的次数
     * @return com.zmn.common.dto.ResponseDTO<java.lang.Boolean>
     * @author wangjie
     * @since 2019/8/2
     **/
    private ResponseDTO<BlacklistRedis> dayBlacklist(String key, String hashKey, Integer restrictionCount) {

        Integer countValue = this.getRedisConfig(key, hashKey);
        if (countValue >= restrictionCount) {
            log.error("参数：key：{}，hashKey：{}下单次数超过系统限制，请拨打客服热线进行预约下单", key, hashKey);
            return ResponseDTO.fail("下单次数超过系统限制，请拨打客服热线进行预约下单");
        }
        // 收集待配置得数据
        BlacklistRedis blacklistRedis = new BlacklistRedis(key, hashKey);
        return ResponseDTO.success(blacklistRedis);
    }

    /**
     * 描述: 时间段限制：（次数/30分钟）手机号限制5次，用户ID限制5次，IP限制300次
     *
     * @param blacklistDio
     *            黑白名单检查入参对象
     * @return com.zmn.common.dto.ResponseDTO<java.lang.Boolean>
     * @author wangjie
     * @since 2019/8/2
     **/
    private ResponseDTO<List<BlacklistRedis>> checkTimeBlacklist(BlacklistDIO blacklistDio, BlacklistConfig config) {

        ResponseDTO<BlacklistRedis> responseDto = ResponseDTO.success();

        List<BlacklistRedis> arrayList = Lists.newArrayList();
        if (Objects.nonNull(blacklistDio.getIp())) {
            responseDto = this.timeBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_IP, blacklistDio.getIp(), config.getIpConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按时间段检查达到限制条件,ip限制,tag:{},msg:{},errorCode:{}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_IP, blacklistDio.getIp(),
                        BrsErrorCodeConsts.ERROR_CODE_IP_COUNT_LIMIT_BY_TIME_RANGE);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_IP_COUNT_LIMIT_BY_TIME_RANGE);
            }
        }
        if (Objects.nonNull(blacklistDio.getMobile())) {
            responseDto = this.timeBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_MOBILE, blacklistDio.getMobile(), config.getMobileConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按时间段检查达到限制条件,mobile限制,tag:{},msg:{},errorCode:{}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_MOBILE, blacklistDio.getMobile(),
                        BrsErrorCodeConsts.ERROR_CODE_MOBILE_COUNT_LIMIT_BY_TIME_RANGE);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_MOBILE_COUNT_LIMIT_BY_TIME_RANGE);
            }
        }
        if (Objects.nonNull(blacklistDio.getUserId())) {
            responseDto = this.timeBlacklist(RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_USER_ID, Optional.ofNullable(blacklistDio.getUserId()).orElse(0L).toString(), config.getUserIdConfig());
            arrayList.add(responseDto.getData());
            if (!responseDto.isSuccess()) {
                log.error("黑白名单,按时间段检查达到限制条件,userId限制,tag:{},msg:{},errorCode:{}", RedisKeyConst.BLACKLIST_SINCE_IP_REDIS_TIME_KEY_USER_ID, blacklistDio.getUserId(),
                        BrsErrorCodeConsts.ERROR_CODE_USERID_COUNT_LIMIT_BY_TIME_RANGE);
                throw new BrsException(BrsErrorCodeConsts.ERROR_CODE_USERID_COUNT_LIMIT_BY_TIME_RANGE);
            }
        }
        log.info("黑白名单,按时间段检查通过,msg{}", JSON.toJSONString(responseDto));
        return ResponseDTO.success(arrayList, responseDto.getMessage());
    }

    /**
     * 描述: 按时间配置
     *
     * @param key
     *            redis Key
     * @param hashKey
     *            散列 key
     * @param restrictionCount
     *            限制的次数
     * @return com.zmn.common.dto.ResponseDTO<java.lang.Boolean>
     * @author wangjie
     * @since 2019/8/2
     **/
    private ResponseDTO<BlacklistRedis> timeBlacklist(String key, String hashKey, Integer restrictionCount) {

        Integer countValue = this.getRedisConfig(key, hashKey);
        if (countValue >= restrictionCount) {
            Long expireMinutes = this.getExpireMilliseconds(key + hashKey);
            String formatTime = formatTime(expireMinutes);
            return ResponseDTO.fail("下单次数超过系统限制，请" + formatTime + "之后再下单");
        }
        // 收集待配置得数据
        BlacklistRedis blacklistRedis = new BlacklistRedis(key, hashKey);
        return ResponseDTO.success(blacklistRedis);
    }

    /**
     * 描述: 根据入参检查白名单
     *
     * @param whiteNumList
     *            入参
     * @return boolean 如果查询到有白名单这返回 true
     * @author wangjie
     * @since 2019/8/2
     **/
    private boolean checkWhitelist(ArrayList<String> whiteNumList) {

        long count = Optional.ofNullable(whitelistService.listByWhiteNum(whiteNumList)).orElse(Collections.emptyList()).size();
        return count > GlobalConsts.NONE;
    }

    /**
     * 描述: 根据入参检查黑名单
     *
     * @param blackNumList
     *            入参
     * @return boolean 如果查询到有白名单这返回 true
     * @author wangjie
     * @since 2019/8/2
     **/
    private boolean checkBlacklist(ArrayList<String> blackNumList) {

        long count = Optional.ofNullable(blacklistService.listByBlackNum(blackNumList)).orElse(Collections.emptyList()).size();
        return count > GlobalConsts.NONE;
    }

    /**
     * 描述: : 根据redis key 与 散列 key 获取redis 配置文件
     *
     * @param key
     *            redis Key
     * @param hashKey
     *            散列 key
     * @return com.zmn.plat.model.entity.blacklist.BlacklistRedis
     * @author wangjie
     * @since 2019/8/9
     **/
    private Integer getRedisConfig(String key, String hashKey) {
        Object o = redisTemplate.opsForHash().get(key + hashKey, hashKey);
        return Objects.isNull(o) ? 0 : Integer.parseInt(o.toString());
    }

    /**
     * 描述: 根据渠道id检查 判断是否为大客户（厂商ID、政企ID、渠道商ID，满足其一就可以直接下单）；如果是，则直接下单；如果不是则判断是否为白名单
     *
     * @param channelId
     *            渠道id
     * @return java.lang.Boolean
     * @author wangjie
     * @since 2019/8/7
     **/
    private Boolean checkChannel(Integer channelId) {

        ChannelDRO channel = channelListRemoteService.getByChannelId(channelId).getData();
        if (Objects.isNull(channel)) {
            return false;
        }
        // 渠道分类：1.政企渠道2.商家渠道3.厂家渠道4.自有渠道5.推广渠道6.服务商渠道
        Integer enterpriseChannel = 1;
        if (Objects.equals(channel.getChannelType(), enterpriseChannel)) {
            log.info("黑白名单,渠道检查通过该渠道为:政企渠道 msg:{}", channelId);
            return true;
        }
        Integer manufacturerChannel = 3;
        if (Objects.equals(channel.getChannelType(), manufacturerChannel)) {
            log.info("黑白名单,渠道检查通过该渠道为:厂家渠道 msg:{}", channelId);
            return true;
        }
        ResponseDTO<Long> resp = passListRemoteService.getUserIdByIdentityIdAndType(channelId.longValue(), PassConsts.PASS_IDENTITY_TYPE_CHANNEL);

        if (resp.isSuccess() && Objects.equals(resp.getStatus(), StatusConsts.STATUS_SUCCESS)) {
            if (Objects.nonNull(resp.getData())) {
                log.info("黑白名单,渠道检查通过该渠道为:渠道商 msg:{}", channelId);
                return true;
            } else {
                return false;
            }
        } else {
            log.info("黑白名单,渠道为渠道商失败 msg:{}", resp.getMessage());
            return false;
        }
    }

    /**
     * 描述: 生效黑白名单
     *
     * @return java.lang.Boolean 生效返回true,不生效返回 false
     * @author wangjie
     * @since 2019/8/8
     **/
    private Boolean effectBlacklist() {
        BlacklistConfig config = Optional.ofNullable(blacklistConfigService.findByTimeType(TIME_BLACKLIST_TYPE)).orElse(new BlacklistConfig());
        return Objects.equals(config.getStatus(), GlobalConsts.YES);
    }

    /**
     * 描述: 批量提交redis 缓存数据 使用 redisTemplate.opsForHash().increment(Long) 自增长
     *
     * @param blacklistRedisList
     *            黑白名单，存入redis数据模型
     * @param timeout
     *            超时时间
     * @param unit
     *            超时单位
     * @author wangjie
     * @since 2019/8/9
     **/
    private void batchSubmitBlacklistRedisConfig(List<BlacklistRedis> blacklistRedisList, Integer timeout, TimeUnit unit) {

        blacklistRedisList.forEach(config -> {
            Long increment = redisTemplate.opsForHash().increment(config.getKey() + config.getHashKey(), config.getHashKey(), GlobalConsts.NO);
            // 计数器为1时配置过期时间
            if (increment == GlobalConsts.NO) {
                redisTemplate.expire(config.getKey() + config.getHashKey(), timeout, unit);
            }
        });
    }

    /**
     * 描述: 根据redis key 返回过期时间 单位为毫秒
     *
     * @param key
     *            redis key
     * @return java.lang.Long
     * @author wangjie
     * @since 2019/8/9
     **/
    private Long getExpireMilliseconds(String key) {
        return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
    }

    /**
     * 描述: 根据入参获取参数设置成集合
     *
     * @param blacklistDio
     *            入参
     * @return java.util.ArrayList<java.lang.String>
     * @author wangjie
     * @since 2019/8/8
     * @modifier zhangyao 2021/04/27 修改黑白名单监控对象逻辑：如果操作对象信息有值，就监控操作对象，操作对象没有值，就监控用户
     **/
    @NotNull
    private ArrayList<String> getBlacklistDioList(BlacklistDIO blacklistDio) {
        ArrayList<String> blackNumList = Lists.newArrayList();
        // 操作对象ID是否为系统
        boolean operatorIdIsSystem = OperateRecordMq.getObjectIdIsSystem(blacklistDio.getOperatorId());
        // 操作对象身份是否不在监控范围内
        boolean isNotMonitoringScope = !OperateRecordMq.getObjectIdentityIsMonitoringScope(blacklistDio.getOperatorIdentity());
        if (Objects.isNull(blacklistDio.getOperatorId()) || operatorIdIsSystem || isNotMonitoringScope) {
            if (Objects.nonNull(blacklistDio.getUserId())) {
                blackNumList.add(Optional.ofNullable(blacklistDio.getUserId()).orElse(0L).toString());
            }
            if (Objects.nonNull(blacklistDio.getMobile())) {
                blackNumList.add(blacklistDio.getMobile());
            }
        } else {
            blackNumList.add(String.valueOf(blacklistDio.getOperatorId()));
            if (StringUtils.isNotEmpty(blacklistDio.getOperatorMobile())) {
                blackNumList.add(blacklistDio.getOperatorMobile());
            } else {
                blackNumList.add(blacklistDio.getMobile());
            }
        }
        if (Objects.nonNull(blacklistDio.getIp())) {
            blackNumList.add(blacklistDio.getIp());
        }
        return blackNumList;
    }

    /**
     * 描述: 将毫秒转换为描述性文本如 1天20小时10分8秒
     *
     * @param ms
     *            毫秒
     * @return java.lang.String
     * @author wangjie
     * @since 2018/12/21 14:37
     */
    private static String formatTime(Long ms) {
        int ss = 1000;
        Integer mi = ss * 60;
        Integer hh = mi * 60;
        Integer dd = hh * 24;

        Long day = ms / dd;
        Long hour = (ms - day * dd) / hh;
        Long minute = (ms - day * dd - hour * hh) / mi;
        long second = (ms - day * dd - hour * hh - minute * mi) / ss;

        StringBuilder sb = new StringBuilder();
        if (day > GlobalConsts.NONE) {
            sb.append(day).append("天");
        }
        if (hour > GlobalConsts.NONE) {
            sb.append(hour).append("小时");
        }
        if (minute > GlobalConsts.NONE) {
            sb.append(minute).append("分钟");
        }
        if (second > GlobalConsts.NONE) {
            sb.append(second).append("秒");
        }
        if (second == GlobalConsts.NONE) {
            sb.append(second).append("秒");
        }
        return sb.toString();
    }
}
